{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "7f738b81-c691-484f-8789-2e81fb918900",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Finding optimal K: 100%|██████████| 40/40 [00:00<00:00, 99.64it/s] \n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "最优K值: 6, 最优准确率: 0.9889\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from sklearn.datasets import load_digits\n",
    "from sklearn.model_selection import train_test_split\n",
    "from sklearn.neighbors import KNeighborsClassifier\n",
    "from sklearn.metrics import accuracy_score\n",
    "import pickle\n",
    "from tqdm import tqdm\n",
    "\n",
    "# 步骤1：加载并拆分数据集\n",
    "digits = load_digits()\n",
    "X = digits.data\n",
    "y = digits.target\n",
    "X_train, X_test, y_train, y_test = train_test_split(\n",
    "    X, y, test_size=0.2, random_state=42\n",
    ")\n",
    "\n",
    "# 步骤2：遍历K值并评估模型\n",
    "k_values = range(1, 41)\n",
    "accuracies = []\n",
    "for k in tqdm(k_values, desc=\"Finding optimal K\"):\n",
    "    knn = KNeighborsClassifier(n_neighbors=k)\n",
    "    knn.fit(X_train, y_train)\n",
    "    y_pred = knn.predict(X_test)\n",
    "    accuracy = accuracy_score(y_test, y_pred)\n",
    "    accuracies.append(accuracy)\n",
    "\n",
    "# 步骤3：确定最优K值\n",
    "max_accuracy = max(accuracies)\n",
    "best_k = k_values[accuracies.index(max_accuracy)]\n",
    "print(f\"最优K值: {best_k}, 最优准确率: {max_accuracy:.4f}\")\n",
    "\n",
    "# 步骤4：绘制并保存准确率折线图\n",
    "plt.figure(figsize=(10, 6))\n",
    "plt.plot(k_values, accuracies, marker='o', linestyle='-', color='blue')\n",
    "plt.axvline(x=best_k, color='red', linestyle='--')\n",
    "plt.text(best_k + 0.5, max_accuracy, f'k={best_k}, Accuracy={max_accuracy:.4f}', color='red')\n",
    "plt.xlabel('k value')\n",
    "plt.ylabel('Accuracy')\n",
    "plt.title('Accuracy of different k values')\n",
    "plt.grid(True)\n",
    "plt.savefig('accuracy_plot.pdf')\n",
    "plt.close()\n",
    "\n",
    "# 步骤5：保存最优KNN模型\n",
    "best_knn = KNeighborsClassifier(n_neighbors=best_k)\n",
    "best_knn.fit(X_train, y_train)\n",
    "with open('best_knn_model.pkl', 'wb') as f:\n",
    "    pickle.dump(best_knn, f)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b416a252-2891-48d4-bb90-d6ad1a26cb19",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import streamlit as st\n",
    "from sklearn.datasets import load_digits\n",
    "from sklearn.model_selection import train_test_split, GridSearchCV\n",
    "from sklearn.neighbors import KNeighborsClassifier\n",
    "import joblib\n",
    "from streamlit_drawable_canvas import st_canvas\n",
    "from PIL import Image\n",
    "\n",
    "# 1. 训练并保存最佳KNN模型（首次运行执行）\n",
    "def train_optimal_knn():\n",
    "    data = load_digits()\n",
    "    X, y = data.data, data.target\n",
    "    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)\n",
    "    \n",
    "    # 网格搜索调优（移除标准化，因load_digits特征已适配）\n",
    "    param_grid = {'n_neighbors': [3,5,7], 'weights': ['uniform', 'distance'], 'metric': ['euclidean']}\n",
    "    grid_search = GridSearchCV(KNeighborsClassifier(), param_grid, cv=5, scoring='accuracy')\n",
    "    grid_search.fit(X_train, y_train)\n",
    "    best_knn = grid_search.best_estimator_\n",
    "    \n",
    "    # 仅保存模型（无scaler文件）\n",
    "    joblib.dump(best_knn, 'best_knn_model.pkl')\n",
    "    print(f\"最佳参数：{grid_search.best_params_}，测试准确率：{best_knn.score(X_test, y_test):.4f}\")\n",
    "\n",
    "# 2. WebAPP界面与预测逻辑\n",
    "def main():\n",
    "    # 首次运行时取消注释以训练模型，之后可注释\n",
    "    # train_optimal_knn()\n",
    "    \n",
    "    # 加载模型（无scaler，因训练时未使用标准化）\n",
    "    best_knn = joblib.load('best_knn_model.pkl')\n",
    "    \n",
    "    st.title(\"手写数字识别（KNN优化版）\")\n",
    "    # 手写画布\n",
    "    canvas_result = st_canvas(\n",
    "        fill_color=\"black\", stroke_color=\"white\", stroke_width=20,\n",
    "        width=280, height=280, drawing_mode=\"freedraw\", key=\"canvas\"\n",
    "    )\n",
    "    \n",
    "    if canvas_result.image_data is not None:\n",
    "        # 图像预处理\n",
    "        img = Image.fromarray(canvas_result.image_data.astype(np.uint8)).convert('L')\n",
    "        img = img.resize((8,8))  # 适配load_digits的8×8尺寸\n",
    "        img_array = np.array(img).flatten() / 255.0  # 归一化到0-1\n",
    "        \n",
    "        # 预测（直接输入，无标准化）\n",
    "        prediction = best_knn.predict([img_array])[0]\n",
    "        confidence = best_knn.predict_proba([img_array]).max()\n",
    "        \n",
    "        st.write(f\"预测结果：{prediction}\")\n",
    "        st.write(f\"置信度：{confidence:.2f}\")\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    main()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "edge-kernel",
   "language": "python",
   "name": "edge-kernel"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
